package org.syntaxlisp.core.match.tree;

import org.syntaxlisp.core.ParseException;
import org.syntaxlisp.core.element.DefID;

import java.util.*;

/**
 * @author HuangYijun
 * @data 2023-10-12 10:04
 **/
public class PureCursorProvider {

    private TreeMap<Integer, SameNextOffsetCursorQueueUnit> cursorQueueMap;

    private Map<Integer, Map<DefID, List<PureCursor>>> refCursorMap;

    private Map<Integer, Map<DefID, List<PureCursor>>> rollbackCandidateRefCursorMap;

    /**
     * def start -> cur offset -> next offset -> match state : ListTreeMap<RepeatFrame, Boolean>
     */
    private CursorMatchHistory cursorMatchHistory;

    /**
     * def start -> cur offset -> next offset -> match state : ListTreeMap<RepeatFrame, Boolean>
     */
    private CursorMatchHistory refCursorMatchHistory;

    public PureCursorProvider() {
        cursorQueueMap = new TreeMap<>();
        refCursorMap = new HashMap<>();
        rollbackCandidateRefCursorMap = new HashMap<>();

        cursorMatchHistory = new CursorMatchHistory();
        refCursorMatchHistory = new CursorMatchHistory();
    }

    public Optional<PureCursor> poll () {
        if (cursorQueueMap.isEmpty()) {
            return Optional.empty();
        }
        Integer largestNextOffset = cursorQueueMap.lastKey();

        SameNextOffsetCursorQueueUnit cursorQueue = cursorQueueMap.get(largestNextOffset);
        PureCursor cursor = cursorQueue.poll();
        if (cursorQueue.isEmpty()) {
            cursorQueueMap.remove(largestNextOffset);
        }
        return Optional.of(cursor);
    }

    public boolean isEmpty () {
        return cursorQueueMap.isEmpty();
    }

    public boolean removeRollbackCandidateRefCursorList (int start, DefID def) {
        Map<DefID, List<PureCursor>> defCandidateMap
                = rollbackCandidateRefCursorMap.getOrDefault(start, Collections.emptyMap());
        boolean removeExisted = (defCandidateMap.remove(def) != null);
        if (defCandidateMap.isEmpty()) {
            rollbackCandidateRefCursorMap.remove(start);
        }
        return removeExisted;
    }

    public boolean offer(PureCursor cursor) {
        if (cursor.isReducingRefCursor()) {
            return offerRefCursor(cursor);
        } else {
            if (!cursor.isReducing()) {
                if (cursorMatchHistory.put(cursor)) {
                    return cursorQueueMap.computeIfAbsent(cursor.getNextOffset(), SameNextOffsetCursorQueueUnit::new)
                                         .offer(cursor);
                } else {
                    return false;
                }
            } else {
                throw new ParseException("Reducing base cursor can not be offered!");
            }
        }
    }

    private boolean offerRefCursor(PureCursor refCursor) {
        if (refCursorMatchHistory.put(refCursor)) {
            List<PureCursor> refCursorList = refCursorMap.computeIfAbsent(refCursor.getCurOffset(),
                                                                          start -> new HashMap<>())
                                                         .computeIfAbsent(refCursor.getRefID(),
                                                                          def -> new ArrayList<>());
            List<PureCursor> rollbackCandidateRefCursorList
                    = rollbackCandidateRefCursorMap.computeIfAbsent(refCursor.getCurOffset(),
                                                                    start -> new HashMap<>())
                                                   .computeIfAbsent(refCursor.getRefID(),
                                                                    def -> new ArrayList<>());
            refCursorList.add(refCursor);
            rollbackCandidateRefCursorList.add(refCursor);
            return true;
        }
        return false;
    }

    public List<PureCursor> consumeRollbackRefCursors () {

        List<PureCursor> consumedCursors = new ArrayList<>();
        rollbackCandidateRefCursorMap.values().forEach(refCursorMap -> {
            refCursorMap.values().forEach(consumedCursors::addAll);
        });
        rollbackCandidateRefCursorMap.clear();
        return consumedCursors;
    }

    public List<PureCursor> getRefCursorSet(Integer startOffset, DefID def) {
        return def != null
               ? refCursorMap.getOrDefault(startOffset, Collections.emptyMap())
                             .getOrDefault(def, Collections.emptyList())
               : Collections.emptyList();
    }

    public Map<DefID, List<PureCursor>> getRefCursorMap(Integer startOffset) {
        return startOffset != null
               ? refCursorMap.getOrDefault(startOffset, Collections.emptyMap())
               : Collections.emptyMap();
    }

    private static class SameNextOffsetCursorQueueUnit {

        int nextOffset;

        Queue<PureCursor> cursorQueue;

        SameNextOffsetCursorQueueUnit(int nextOffset) {
            this.nextOffset = nextOffset;
            cursorQueue = new ArrayDeque<>();
        }

        PureCursor poll() {
            return cursorQueue.poll();
        }

        boolean offer(PureCursor cursor) {
            return cursorQueue.offer(cursor);
        }

        boolean isEmpty () {
            return cursorQueue.isEmpty();
        }
    }

}
